<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>分片上传页面</title>
    <script src="https://cdn.jsdelivr.net/npm/spark-md5@3.0.1/spark-md5.min.js"></script>
    <style>
        body { font-family: Arial; padding: 20px; }
        progress { width: 100%; height: 20px; }
    </style>
</head>
<body>

<h2>文件分片上传</h2>
<input type="file" id="fileInput" />
<button id="uploadBtn" disabled>开始上传</button>
<p id="status">等待选择文件...</p>
<progress id="progressBar" value="0" max="100"></progress>

<script>
    const fileInput = document.getElementById('fileInput');
    const uploadBtn = document.getElementById('uploadBtn');
    const statusText = document.getElementById('status');
    const progressBar = document.getElementById('progressBar');

    const CHUNK_SIZE = 1024 * 1024 * 5; // 5MB per chunk

    let file = null;
    let fileMd5 = '';
    let uploadedChunks = [];

    fileInput.addEventListener('change', async () => {
        file = fileInput.files[0];
        if (!file) return;

        statusText.textContent = '正在计算文件MD5...';
        fileMd5 = await calculateFileMD5(file);
        statusText.textContent = `文件MD5：${fileMd5}`;
        uploadBtn.disabled = false;
    });

    uploadBtn.addEventListener('click', () => {
        if (!file) return alert('请先选择文件');
        startUpload();
    });

    async function calculateFileMD5(file) {
        return new Promise((resolve) => {
            const spark = new SparkMD5.ArrayBuffer();
            const reader = new FileReader();

            let chunkSize = CHUNK_SIZE;
            let currentChunk = 0;

            function loadNext() {
                const start = currentChunk * chunkSize;
                const end = Math.min(start + chunkSize, file.size);
                const fileChunk = file.slice(start, end);

                reader.readAsArrayBuffer(fileChunk);
            }

            reader.onload = (e) => {
                spark.append(e.target.result);
                currentChunk++;

                if (currentChunk * chunkSize >= file.size) {
                    resolve(spark.end());
                } else {
                    loadNext();
                }
            };

            loadNext();
        });
    }

    const baseURL = 'http://localhost:20040/filemger';

    async function getUploadedChunks() {
        const res = await fetch(baseURL + `/upload-status?fileMd5=${encodeURIComponent(fileMd5)}`);
        if (res.ok) {
            uploadedChunks = await res.json();
        }
    }

    async function startUpload() {
        statusText.textContent = '检查上传状态...';
        await getUploadedChunks();

        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        progressBar.max = totalChunks;

        // 并发控制参数
        const CONCURRENCY = 5; // 最大并发数
        let uploadedCount = 0;
        let currentChunkIndex = 0;
        const chunkPromises = [];

        // 创建一个并发上传任务
        const uploadNextChunk = async () => {
            if (currentChunkIndex >= totalChunks) return;

            const index = currentChunkIndex++;
            if (uploadedChunks.includes(index)) {
                progressBar.value = ++uploadedCount;
                return uploadNextChunk();
            }

            const start = index * CHUNK_SIZE;
            const end = Math.min(start + CHUNK_SIZE, file.size);
            const chunk = file.slice(start, end);

            const formData = new FormData();
            formData.append('file', chunk);
            formData.append('chunkIndex', index);
            formData.append('fileMd5', fileMd5);
            formData.append('fileName', file.name);

            statusText.textContent = `上传分片 ${index + 1} / ${totalChunks}`;

            try {
                const res = await fetch(baseURL + '/upload-chunk', {
                    method: 'POST',
                    body: formData
                });

                if (!res.ok) throw new Error(`上传失败: 分片 ${index}`);

                progressBar.value = ++uploadedCount;
            } catch (err) {
                statusText.textContent = `上传出错: 分片 ${index}, 错误: ${err.message}`;
                uploadedChunks.push(index); // 可记录失败重试逻辑
            } finally {
                await uploadNextChunk(); // 继续下一个任务
            }
        };

        // 启动初始并发任务
        for (let i = 0; i < CONCURRENCY; i++) {
            chunkPromises.push(uploadNextChunk());
        }

        await Promise.all(chunkPromises);

        statusText.textContent = '合并分片中...';
        const mergeRes = await fetch(baseURL + '/merge-chunks', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                fileMd5,
                fileName: file.name
            })
        });

        if (mergeRes.ok) {
            statusText.textContent = '上传完成！';
            progressBar.value = progressBar.max;
        } else {
            statusText.textContent = '合并失败，请重试';
        }
    }

</script>

</body>
</html>
